﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Kokomo.Mirror.PeFormat.Resources
{
	public class ResourceDataDescriptor : ResourceItem
	{
		public ResourceDataDescriptor(
			PeModule module,
			ResourcePath path,
			uint rva,
			int size,
			int codePage
			)
			: base(module, path)
		{
			this.Rva = rva;
			this.Size = size;
			this.CodePage = codePage;
		}

		public uint Rva { get; private set; }
		public int Size { get; private set; }
		public int CodePage { get; private set; }

		private string GetResourcePartText(int index)
		{
			if (index < this.Path.Parts.Length)
			{
				ResourcePathPart part = this.Path.Parts[index];
				return part.ToString();
			}
			else
			{
				return null;
			}
		}

		public string ResourceType
		{
			get { return this.GetResourcePartText(0); }
		}
		public string NameOrId
		{
			get { return this.GetResourcePartText(1); }
		}
		public string Language
		{
			get { return this.GetResourcePartText(2); }
		}

		public byte[] GetData()
		{
			byte[] rawData = this.GetRawData();
			byte[] data;
			switch (this.PredefinedType)
			{
				//case PeFormat.ResourceType.Icon:
				case Resources.ResourceType.Bitmap:
					{
						MemoryStream stream = new MemoryStream(rawData.Length + 0x0E);
						BinaryWriter writer = new BinaryWriter(stream, Encoding.Default);
						writer.Write(new char[] { 'B', 'M' });
						writer.Write((rawData.Length + 10));
						writer.Write((short)0);
						writer.Write((short)0);
						writer.Write(0x36);	// Offset to data
						writer.Write(rawData);
						writer.Flush();

						data = stream.ToArray();
					}
					break;
				default:
					data = rawData;
					break;
			}

			return data;
		}
		public byte[] GetRawData()
		{
			uint? fileOffset = this.Module.RvaToFileOffset(this.Rva);
			if (!fileOffset.HasValue) throw new InvalidOperationException("Resource has invalid RVA");

			byte[] buffer = new byte[this.Size];
			using (Stream stream = this.Module.GetStream())
			{
				stream.Position = fileOffset.Value;
				stream.Read(buffer, 0, buffer.Length);
			}

			return buffer;
		}

		public object LoadObject()
		{
			ResourceType predefinedType = this.PredefinedType;

			object obj = null;
			switch (predefinedType)
			{
				case Resources.ResourceType.Dialog:
					obj = this.LoadDialogTemplate();
					break;
			}

			return obj;
		}

		private DialogTemplate LoadDialogTemplate()
		{
			MemoryStream stream = new MemoryStream(this.GetRawData());
			StructSerializer serializer = new StructSerializer(Encoding.Unicode);

			BinaryReader reader = new BinaryReader(stream);
			DLGTEMPLATE dlgtemplate = serializer.ReadStruct<DLGTEMPLATE>(stream);

			DialogTemplate template = new DialogTemplate()
			{
				WindowStyle = dlgtemplate.style,
				DialogStyle = (DialogStyles)dlgtemplate.style,
				ExtendedWindowStyle = dlgtemplate.dwExtendedStyle,
				Left = dlgtemplate.x,
				Top = dlgtemplate.y,
				Width = dlgtemplate.cx,
				Height = dlgtemplate.cy
			};

			object menuID = this.ReadIdOrString(reader);
			object classID = this.ReadIdOrString(reader);
			if (classID is string)
				template.ClassName = (string)classID;

			object titleID = this.ReadIdOrString(reader);
			if (titleID is string)
				template.Text = (string)titleID;

			if (template.DialogStyle.HasFlag(DialogStyles.UserFont))
			{
				ushort pointSize = reader.ReadUInt16();
				string fontFamily = reader.ReadNullTerminatedString();
			}

			for (int i = 0; i < dlgtemplate.cdit; i++)
			{
				DLGITEMTEMPLATE dlgitemtemplate = serializer.ReadStruct<DLGITEMTEMPLATE>(stream);

				DialogItemTemplate itemTemplate = new DialogItemTemplate()
				{
					WindowStyle = dlgtemplate.style,
					ExtendedWindowStyle = dlgtemplate.dwExtendedStyle,
					Left = dlgtemplate.x,
					Top = dlgtemplate.y,
					Width = dlgtemplate.cx,
					Height = dlgtemplate.cy
				};

				object itemClassID = this.ReadIdOrString(reader);
				if (classID is ushort)
					itemTemplate.ItemType = (DialogItemType)(ushort)itemClassID;
				object itemTitleID = this.ReadIdOrString(reader);
				if (itemTitleID is string)
					itemTemplate.Text = (string)itemTitleID;

				ushort creationDataSize = reader.ReadUInt16();
				byte[] creationData = null;
				if (creationDataSize > 0)
				{
					creationData = new byte[creationDataSize - 2];
					stream.Read(creationData, 0, creationData.Length);
				}
				itemTemplate.CreationData = creationData;

				template.Items.Add(itemTemplate);
			}

			return template;
		}

		private object ReadIdOrString(BinaryReader reader)
		{
			object nameOrID = null;

			ushort initial = reader.ReadUInt16();
			if (initial == 0xFFFF)
			{
				nameOrID = reader.ReadUInt16();
			}
			else if (initial != 0)
			{
				reader.BaseStream.Position -= 2;
				nameOrID = reader.ReadNullTerminatedString();
			}

			return nameOrID;
		}
	}
}
